"""What a fantastic module"""
import os.path

from PyQt4.QtCore   import  QObject, Qt, pyqtSignal, QUrl, QCoreApplication, \
                            QRect, QPoint, QEvent, pyqtProperty, QByteArray, \
                            QString

from PyQt4.QtGui    import  QApplication, QMainWindow, QDockWidget, QPixmap, \
                            QPainter, QMouseEvent

from PyQt4.QtWebKit import QWebView, QWebSettings, QWebInspector, QWebPage
from PyQt4.QtNetwork import QNetworkRequest, QNetworkDiskCache
import sip

from wedewoba.Timer import Timer
from wedewoba.network import NetworkAccessManager
from wedewoba.CookieJar import CookieJar


class ExchangeObj(QObject):
    """ base class to store Python/Qt data which is to be exposed to javascript and vis versa"""

    def __init__(self, parent=None, data={}):
        super(ExchangeObj, self).__init__(parent)
        self._data = data
    
    def expose(self, frame, objName="qtExchangeObj", dataName="qtData"):
        """ expose the data to the given frame

            the exchange object will be exposed as objName
            the data will be exposed as dataName
        """
        def ex():
            """ wrapper function """
            frame.addToJavaScriptWindowObject(objName, self)
            frame.evaluateJavaScript(dataName+' = '+objName+'.data')

        frame.javaScriptWindowObjectCleared.connect(ex)

    @pyqtProperty("QVariantMap")
    def data(self):
        """ get/set the the data of the exchange object """
        return self._data

    @data.setter
    def data(self, value):
        """get/set the the data of the exchange object"""
        self._data = value






class PageBrowser(QObject):
    """ class that mimiks a webbrowser

        it is the main object a developer will interact with
        it provides helper methods for interacting with the webpage
        it provides methods and signals to interact with the webpage on all 
        stages of the page loading 

    """

    loadFinished = pyqtSignal(bool)
    """ :param bool loadOk: True if no error occoured else False

        signal/event emmited when page load is finished
    """
    loadProgress = pyqtSignal(int)
    """ :param int progress: range(0,100)

        signal/event emmited when page load progressed
    """
    loadStarted  = pyqtSignal()
    """ signal/event emmited when page load started """
    loadTimeout  = pyqtSignal()
    """ signal/event emmited when page load timed out """
    #loadError    = pyqtSignal()
    urlChanged   = pyqtSignal(QUrl)
    """ signal/event emmited when url of page changed """


    def __init__(
            self, 
            debug                   = False,
            enableJavascript        = True,
            enablePlugins           = True,
            cachePath               = None,
            cookieJarFile           = None
        ):

        super(PageBrowser, self).__init__(None)

        if not QApplication.instance():
            raise RuntimeError(
                    self.__class__.__name__ + 
                    " requires a running QApplication instance"
                )



        #self.__reset()
        self.__loadStartedCounter = 0
        self.__loadFinishedCounter = 0
        self.__loadingTimeout = False

        self.url = None
        self.window                 = QMainWindow()
        self.view                   = QWebView()
        self.page                   = WebPage()
        self.NetworkAccessManager   = NetworkAccessManager()

        self.webSettings            = self.view.settings()

        self.PageLoader             = PageLoader(
                                            page=self.page, 
                                            NAM = self.NetworkAccessManager
                                        )

        if cachePath != None and type(cachePath) == str:
            if os.path.isdir(cachePath) and os.access(cachPath, os.W_OK):
                cache = QNetworkDiskCache()
                cache.setCacheDirectory(cachePath)
                self.NetworkAccessManager.setCache(cache)
            else:
                raise RuntimeError(cachePath+" is not writable!")


        if cookieJarFile != None and type(cookieJarFile) == str:
            if not os.path.isfile(cookieJarFile):
                if not os.access(os.path.dirname(cookieJarFile), os.W_OK):
                    raise RuntimeError(os.path.dirname(cookieJarFile)+" is not writable!")
            elif not os.access(cookieJarFile, os.W_OK):
                raise RuntimeError(cookieJarFile+" is not writable!")
            
            self.cookieJar = CookieJar(filename=cookieJarFile)
            self.NetworkAccessManager.setCookieJar(self.cookieJar)


        self.__initPage(enableJavascript, enablePlugins)
        self.__initEvents()

        self.view.setPage(self.page)

        self.qtExchange = ExchangeObj()
        self.qtExchange.objectName()
        #self.qtExchange.expose(self.page.mainFrame())

        if debug:
            self._initDebug()

        self.window.setCentralWidget(self.view)
        self.window.show()



    def __initPage(self, enableJavascript=True, enablePlugins=True):
        self.setWebSetting( QWebSettings.JavascriptEnabled          , enableJavascript )
        self.setWebSetting( QWebSettings.PluginsEnabled             , enablePlugins )
        self.setWebSetting( QWebSettings.PrivateBrowsingEnabled     , False )
        self.setWebSetting( QWebSettings.JavascriptCanOpenWindows   , False )

        self.page.javaScriptConfirm     = False
        self.page.javaScriptAlert       = False

        self.page.mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
        self.page.mainFrame().setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAlwaysOff)

        self.page.javaScriptConsoleMessage = self.__javaScriptConsoleMessage


    def __initEvents(self):

        #self.NetworkAccessManager.sslErrors.connect( self.__onSslErrors )
        #self.NetworkAccessManager.finished.connect( self.__onReply )
        self.view.loadStarted.connect(  self.__onloadStarted    )
        self.view.loadProgress.connect( self.__onloadProgress  ) 
        self.view.loadFinished.connect( self.__onloadFinished  )
        self.view.urlChanged.connect(   self.__onUrlChanged    )

    def _initDebug(self):
        #self.close = False
        inspector = QWebInspector()
        inspector.setPage(self.page)
        dw = QDockWidget() 
        dw.setWidget(inspector)
        try:
            QCoreApplication.setOrganizationName("wedewoba") 
            QCoreApplication.setApplicationName("wedewoba")
            self.page.settings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
            self.window.addDockWidget(Qt.BottomDockWidgetArea, dw)
        except Exception, e:
            print e


    def __javaScriptConsoleMessage(self, msg, ln, sid):
        print msg, ln, sid


    def __onloadStarted(self):
        if not self.__loadStartedCounter:
            self.loadStarted.emit()
            self.onloadStarted()
        self.__loadStartedCounter += 1


    def __onloadProgress(self, progress):
        self.loadProgress.emit(progress)
        self.onloadProgress(progress)


    def __onloadFinished(self, result):
        if self.__loadingTimeout: return 
        self.__loadFinishedCounter += 1
        self.wait()
        if self.__loadFinishedCounter == self.__loadStartedCounter:
            self.loadFinished.emit(result)
            self.onloadFinished(result)

    def __onUrlChanged(self, url):
        self.onUrlChanged(url)
        self.urlChanged.emit(url)


    def __onloadTimeout(self):
        self.__loadingTimeout = True
        self.onloadTimeout()
        self.loadTimeout.emit()

    def __reset(self):
        self.__loadStartedCounter = 0
        self.__loadFinishedCounter = 0
        self.__loadingTimeout = False



    def replace(self, original, replacement):
        """ :param string original: regex or filename
            :param string replacement: filename

            replace original file content by replacement file content
        """
        self.PageLoader.replace(original, replacement)

    def inject(self, src):
        filename, ext = os.path.splitext(src)
        if ext != '' and ext.lower() in (".css", ".js"):
            self.PageLoader.inject(ext[1:].lower(), src)


    def setWebSetting(self, key, value):
        """ set QtWebSettings """
        self.webSettings.setAttribute(key, value)


    def loadPage(self, url, timeout=60):
        """ :param url:
            :type url: string or QUrl
            :param integer timeout: loading timeout in seconds

            start loading given URL
        """
        self.url = QUrl(url)
        if not self.url.scheme():
            self.url.setUrl("http://"+self.url.toString())
        self.preLoad()
        self.PageLoader.loadPage(self.url, timeout)


    def getPageSize(self):
        """ :return: QSize

            get the size in pixels of the page

        """
        return self.view.page().mainFrame().contentsSize()


    def wait(self):
        """ process all pending events """
        while QApplication.hasPendingEvents(): 
            QCoreApplication.processEvents()


    def getElementImage(self, element=None):
        """ :return: QPixmap
            :param element:
            :type element: QWebElement or None

            get an image of the given QWebElement
        """

        mainFrame = self.page.mainFrame()
        contentsSize = mainFrame.contentsSize()
        self.view.setFixedSize(contentsSize)
        
        pixmap = QPixmap(contentsSize)
        painter = QPainter(pixmap)
        mainFrame.render(painter)
        painter.end()

        if element:
            rect = self.getAbsoluteElementGeometry(element)
        else:
            rect = QRect(QPoint(), contentsSize)

        return pixmap.copy(rect)


    def getAbsoluteElementGeometry(self, element):
        """ :return: QRect
            :param QWebElement element:
        
            get the absolute position and size 
            of the given QWebElement within the page
        """
        rect = element.geometry()
        f = element.webFrame()
        if not f:
            f = self.page.mainFrame()
        while f != self.page.mainFrame():
            rect.translate(f.pos())
            f = f.parentFrame()
        return rect


    def findAllElements(self, selector, frame=None):
        """ :return: QWebElementCollection
            :param selector: CSS3 selector
            :param frame:
            :type frame: QWebFrame or None
        
            get all QWebElements found by selector of given
            frame and all child frames
        """
        if not frame:
            frame = self.page.mainFrame()
        elements = frame.findAllElements(selector)
        for f in frame.childFrames():
            elements += self.findAllElements(selector, f)
        return elements


    def findFirstElement(self, selector, frame=None):
        """ :return: QWebElement
            :param selector: CSS3 selector
            :param frame:
            :type frame: QWebFrame or None

            get first QWebElement found by selector in given 
            frame and all child frames
        """
        if not frame:
            frame = self.page.mainFrame()
        element = frame.findFirstElement(selector)
        if element.isNull():
            for f in frame.childFrames():
                element = self.findFirstElement(selector, f)
                if not element.isNull(): 
                    break
        return element


    def clickElement(self, element):
        """ :param QWebElement element:

            send a mouse click event to page on center position of given element 
        """
        pos = QPoint(element.geometry().center())
        button = Qt.LeftButton
        modK = Qt.NoModifier
        self.__sendMouseEvent(QEvent.MouseButtonPress, pos, button, button, modK)

    def __sendMouseEvent(self, eventType, pos, button, buttons, modKey):
        QApplication.sendEvent(self.page, 
            QMouseEvent(eventType, pos, button, buttons, modKey)
        )

        #QEvent::MouseButtonDblClick 4   Mouse press again (QMouseEvent).
        #QEvent::MouseButtonPress    2   Mouse press (QMouseEvent).
        #QEvent::MouseButtonRelease  3   Mouse release (QMouseEvent).
        #QEvent::MouseMove           5   Mouse move (QMouseEvent).



    def preLoad(self):
        """ callback method, will be called just before page is loaded """
        pass

    def onloadStarted(self):
        """ callback method corresponding with the 'loadStarted' event """
        pass

    def onloadProgress(self, progress):
        """ callback method corresponding with the 'loadProgress' event """
        pass

    def onloadFinished(self, result):
        """ callback method corresponding with the 'loadFinished' event """
        pass

    def onUrlChanged(self, url):
        """ callback method corresponding with the 'urlChanged' event """
        pass

    #def onSuccesss(self):
        #pass

    #def onError(self):
        #pass




class PageLoadError(Exception):
    pass
    



class PageLoader(QObject):
    """ class that handels the loading and data interaction of a webpage """

    injectionFinished   = pyqtSignal()
    loadingTimeout      = pyqtSignal()
    loadingError        = pyqtSignal(int, str)
    loadingFinished     = pyqtSignal(QByteArray)


    def __init__(self, 
            page=None,
            NAM=None
        ):
        parent = None

        super(PageLoader, self).__init__(parent)

        self.page = page if page else WebPage()
        self.NetworkAccessManager = NAM if NAM else NetworkAccessManager()

        self.__pageSrc = None
        self.url = None
        self.__loadingTimeout = False
        self.__pageLoaded = False
        self.__injectData = []

        self.timer = Timer()

        self.page.setNetworkAccessManager(self.NetworkAccessManager)


    def replace(self, original, replacement):
        """ return void

            replace the original file data by the replacement file data
        """
        self.NetworkAccessManager.setReplacePattern(original, replacement)


    def loadPage(self, url, timeout=60):
        """ return void
             
            start loading given URL 
        """
        self.url = QUrl(url)
        self.__pageLoaded = False
        self.__pageSrc = None

        def cb(data):
            s = ''
            pos = data.indexOf("</head>")
            if pos < 0: pos = data.indexOf("<body>")
            if pos >= 0:
                for d in self.__injectData:
                    print self.__getTag(**d)
                    s += self.__getTag(**d)+"\n"
                data.insert(pos, s)
            return data

        request = QNetworkRequest(QUrl(url))
        reply = self.NetworkAccessManager.get(request, True, cb)
        reply.error.connect(lambda e: self.__onSrcLoadError(reply, e))
        reply.finished.connect(lambda: self.__onloadSrcFinished(reply, request))

        if timeout:
            self.timer.start(timeout)
            self.timer.timeout.connect(lambda: self.__onloadTimeout(reply) )
    

    def __onSrcLoadError(self, reply, error):
        if error:
            self.timer.timeout.disconnect()
            reply.finished.disconnect()
            reply.abort()
            reply.deleteLater()
            self.loadingError.emit(reply.error(), reply.errorString())


    def __onloadSrcFinished(self, reply, request):
        self.timer.timeout.disconnect()
        src = reply.readAll()
        try:
            self.__pageSrc = QByteArray(unicode(src, "utf-8"))
        except UnicodeEncodeError:
            self.__pageSrc = QByteArray(unicode(src, "latin-1"))

        self.page.mainFrame().setContent(self.__pageSrc, "text/html", reply.url())
        self.__pageLoaded = True
        self.loadingFinished.emit(self.__pageSrc)
        reply.deleteLater()


    def __onloadTimeout(self, reply):
        self.__loadingTimeout = True
        reply.finished.disconnect()
        reply.error.disconnect()




    def inject(self, type, src=None, dataString=None, asFile=False):
        """ return void
        
            inject js/css data into webpage
        """
        data = {
            "type": type,
            "src": src,
            "dataString": dataString,
            "asFile": asFile
        }
        
        if self.__pageLoaded:
            print "inject after"
            self.__injectDataIntoPage(data)
        else:
            print "inject pre"
            self.__injectData.append(data)



    def __injectDataIntoPage(self, data, frame=None):
        if not frame: frame = self.page.mainFrame()
        if data['type'] == "js":
            self.__injectJS(frame=frame, **data)
        elif data['type'] == "css":
            html = self.__getTag(**data)
            frame.getFirstElement('head').appendInside(html)
       




    def __genTag(self, tagName, content, **kwargs):
        voidTags = [ "area", "base", "br", "col", "command",
                     "embed", "hr", "img", "input", "keygen",
                     "link", "meta", "param", "source", "track", 
                     "wbr"
                    ]
        attrs = []
        for k in kwargs:
            attrs.append('{}="{}"'.format(k, kwargs[k]))

        tag = "<{}".format(tagName) + ' '+' '.join(attrs)

        if tagName in voidTags:
            tag += '/>'
        else:
            tag += ">" + content + '</{}>'.format(tagName)

        return tag
        
            

    def __getTag(self, type, src=None, dataString=None, asFile=False):

        if type == "js":
            tagD = { "tagName": "script", "type": "text/javascript" }
            tagD2 = { "src": src }
        else:
            tagD = { "tagName": "style", "type": "text/css", "rel": "stylesheet" }
            tagD2 = { "tagName": "link", "href": src }

        html = ''
        if dataString:
            tagD['content'] = dataString
            html += self.__genTag(**tagD)
            del tagD['content']

        if src:
            if asFile:
                html += self.__genTag(**tagD.update(tagD2))
            else:
                with open(src, "r") as f:
                    tagD['content'] = f.read()
                    html += self.__genTag(**tagD)

        return QString(html)


    def __injectJS(self, type, src=None, dataString=None, asFile=False, frame=None):
        if not frame: frame = self.page.mainFrame()
        self.__injectJSInjector(frame)
        if dataString:
            frame.documentElement().evaluateJavaScript('__wedewoba__injectJS("'+dataString.replace('"', '\"')+'")')
        if src:
            if asFile:
                frame.documentElement().evaluateJavaScript('__wedewoba__injectJSFile("'+src+'")')
            else:
                with open(src, "r") as f:
                    script = f.read().replace('"', '\\"').replace("\n",'\\n')
                    frame.documentElement().evaluateJavaScript('__wedewoba__injectJS("'+script+'")')
        self.wait()

    def __injectJSInjector(self, frame=None):
        if not frame: frame = self.page.mainFrame()

        js = """
            function __wedewoba__injectJSFile (src) {
                var el = document.createElement('script');
                el.type = 'text/javascript';
                el.src = src;
                document.getElementsByTagName('head')[0].appendChild(el);
            }

            function __wedewoba__injectJS (string) {
                var el = document.createElement('script');
                el.type = 'text/javascript';
                el.text = string
                document.getElementsByTagName('head')[0].appendChild(el);
            }
        """
        frame.documentElement().evaluateJavaScript(js)
        self.wait()
        #with open('injectJS.js', "r") as f:
        #    frame.documentElement().evaluateJavaScript(f.read())
        #    self.wait()


    def wait(self):
        while QApplication.hasPendingEvents(): 
            QCoreApplication.processEvents()



class WebPage(QWebPage):

    def __init__(self):
        super(WebPage, self).__init__()
        self.__supportedExtensions = []

    def __castExtension(self, extension, option, output):
        if extension == QWebPage.ChooseMultipleFilesExtension:
            option = sip.cast(option, QWebPage.ChooseMultipleFilesExtensionOption)
            output = sip.cast(output, QWebPage.ChooseMultipleFilesExtensionReturn)
        elif extension == QWebPage.ErrorPageExtension:
            option = sip.cast(option, QWebPage.ErrorPageExtensionOption)
            output = sip.cast(output, QWebPage.ErrorPageExtensionReturn)

        return extension, option, output

    #def extension(self, extension, option=None, output=None):
        #extension, option, output = self.__castExtension(extension, option, output)
        #print extension, option, output

        #return True


    def supportsExtension(self, extension):
        if extension in self.__supportedExtensions:
            return True
        else:
            return False

    def setExtensionSupport(self, extension):
        self.__supportedExtension.append(extension)
